When debugging a program or when analyzing a crash-dump, a developer wants to know where the program is at in its execution. This problem is well-understood and solved for traditional, synchronous programs using traditional call stacks. However, many programmers are moving to asynchronous (async) programming. In asynchronous programming, synchronous call stacks do not accurately provide information about where the program is and how it got there.
Debugging tools or error-reporting-tools may identify which sequence of method-invocations led to a given place within code execution (“ultimate causes”), and where those method invocations were. In traditional synchronous programs, methods exist to obtain synchronous call stacks, which provide a way to tell where the synchronous code will return and in what sequence. Traditional synchronous call stacks are useful because “where code will return” is usually adjacent to “where code was invoked” and because the sequence of returns is usually the opposite of the sequence of invocations. Accordingly, a traditional synchronous call stack is a good proxy for what users, such as developers, want to know when analyzing code. However, in asynchronous programs, the use of traditional synchronous call stacks is no longer a good proxy for what the user wants to know because they typically do not show the ultimate cause within a program, such as a method that kicked off a long-running task.